﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
using System.Threading;

namespace Sharp.Interprocess
{
    public class SharedCollection<T> : BaseSharedCollection<T>,ICollection<T>, IDisposable
    {

        #region Private Members
        private readonly IEqualityComparer<T> m_ItemEqualityComparer;
        #endregion

        #region Constructor(s)

        public SharedCollection(string collectionMapName, int collectionOffset = 0, bool shouldAutoExpand = true, int collectionMaxSize = PAGED_MEMORY_FILE_DEFAULT_SIZE)
            : this(collectionMapName, collectionOffset, EqualityComparer<T>.Default,shouldAutoExpand, collectionMaxSize)
        {            
        }

        public SharedCollection(string collectionMapName,int collectionOffset,IEqualityComparer<T> equalityComparer,bool shouldAutoExpand = true,int collectionMaxSize = PAGED_MEMORY_FILE_DEFAULT_SIZE)
            : base(collectionMapName,collectionOffset,shouldAutoExpand,collectionMaxSize)
        {
            m_ItemEqualityComparer = equalityComparer;
        }

        #endregion

        public void AddOrUpdate(T item)
        {
            try
            {
                m_ReadWriteMutex.WaitOne();
                var storedData = GetAllItems().ToList();
                if (storedData.Any(storedItem => m_ItemEqualityComparer.Equals(storedItem, item)))
                {
                    storedData.RemoveAll(storedItem => m_ItemEqualityComparer.Equals(storedItem, item));
                    storedData.Add(item);
                    ClearAllItems();
                    AddRange(storedData);
                }
                else
                {
                    StoreItem(item);
                }
                
            }
            finally
            {
                m_ReadWriteMutex.ReleaseMutex();
            }
        }

        public void AddRange(IEnumerable<T> items)
        {
            foreach (var item in items)
            {
                Add(item);
            }
        }

        #region ICollection Implementation

        public void Add(T item)
        {
            StoreItem(item);
        }

        public void Clear()
        {
            ClearAllItems();
        }

        public bool Contains(T item)
        {
            var storedData = GetAllItems();
            return storedData.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            var storedData = GetAllItems();
            storedData.ToList().CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get 
            {
                try
                {
                    m_ReadWriteMutex.WaitOne();
                    return m_CollectionHeader.ItemCount;
                }
                finally
                {
                    m_ReadWriteMutex.ReleaseMutex();
                }
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(Func<T, bool> itemPredicate)
        {
            bool isRemoved = false;
            try
            {
                m_ReadWriteMutex.WaitOne();
                var fetchedStoredData = GetAllItems();
                var dataWithoutRemovedItem = fetchedStoredData.Where(fetchedItem => !itemPredicate(fetchedItem)).ToList();
                isRemoved = dataWithoutRemovedItem.Count < fetchedStoredData.Count();
                if (isRemoved)
                {
                    Clear();
                    dataWithoutRemovedItem.ForEach(fetchedItem => StoreItem(fetchedItem));
                }
            }
            finally
            {
                m_ReadWriteMutex.ReleaseMutex();
            }

            try
            {
                m_CollectionCountMutex.WaitOne();
                m_CollectionHeader.UpdateOnCollectionChanged();
            }
            finally
            {
                m_CollectionCountMutex.ReleaseMutex();
            }
            return isRemoved;
        }

        public bool Remove(T item)
        {
            return Remove(fetchedItem => m_ItemEqualityComparer.Equals(item, fetchedItem));
        }

        public IEnumerator<T> GetEnumerator()
        {
            return GetAllItems().GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Helper Methods

        protected override void ClearAllItems()
        {
            try
            {
                m_ReadWriteMutex.WaitOne();
                m_CollectionHeader.SetEmptyCollectionValues();
            }
            finally
            {
                m_ReadWriteMutex.ReleaseMutex();
            }
        }

        protected override void StoreItem(T item,bool checkMemoryMappedFileCapacity = true)
        {
            var mmObject = new MemoryMappedObject<T>(item);
            if (checkMemoryMappedFileCapacity)
            {
                EnlargeMemoryMappedFileSizeIfNeeded(mmObject.StoredSerializedObjectLength);
            }

            try
            {                
                m_ReadWriteMutex.WaitOne();
                mmObject.WriteToMemoryMappedFile(m_CollectionStorageFile,m_CollectionHeader.ItemLastOffset);
                m_CollectionHeader.UpdateOnItemAdded(mmObject.StoredSerializedObjectLength);
            }
            finally
            {
                m_ReadWriteMutex.ReleaseMutex();
            }

            try
            {
                m_CollectionCountMutex.WaitOne();
                m_CollectionHeader.UpdateOnCollectionChanged();
            }
            finally
            {
                m_CollectionCountMutex.ReleaseMutex();
            }
        }

        protected override List<T> GetAllItems()
        {
            try
            {
                m_ReadWriteMutex.WaitOne();
                var itemCount = m_CollectionHeader.ItemCount;
                var fetchedData = new List<T>(itemCount);
                var currentOffset = m_CollectionHeader.GetEmptyCollectionItemLastOffset();
                for (int i = 0; i < itemCount; i++)
                {
                    var mmObject = new MemoryMappedObject<T>(m_CollectionStorageFile, currentOffset);
                    fetchedData.Add(mmObject.StoredObject);
                    currentOffset += (mmObject.StoredSerializedObjectLength);
                }

                return fetchedData;
            }
            finally
            {
                m_ReadWriteMutex.ReleaseMutex();
            }
        }

        #endregion
    }
}
